package edu.ucla.cs.typecast.ping;

import java.io.*;
import java.net.*;

import edu.ucla.cs.typecast.net.NetUtil;
import edu.ucla.cs.typecast.net.TypeCastConstants;

import junit.framework.AssertionFailedError;
import junit.framework.TestCase;

/**
 * A ping receiver for testing purpose. It uses the default public address/port
 * and localhost as it private host. It counts all pings it receives.
 */

class CountingPingReceiver extends PingReceiverImpl {

	private static final long serialVersionUID = 1L;

	protected int receiveCount = 0;

	public CountingPingReceiver(int privatePort) throws IOException {
		super(NetUtil.getLocalHost(), privatePort, InetAddress
				.getByName(TypeCastConstants.DEFAULT_RMI_ADDRESS),
				TypeCastConstants.DEFAULT_RMI_PORT);
	}

	public int getReceiveCount() {
		return receiveCount;
	}

	public void receivePing(InetAddress senderHost, int senderPort, int seqNum)
			throws IOException {
		super.receivePing(senderHost, senderPort, seqNum);
		receiveCount++;
		System.err.println("Received ping from " + senderHost + ":"
				+ senderPort + " (I'm PR: " + privateHost + ":" + privatePort
				+ ")" + ", seq # " + seqNum);
		System.err.println("Sending pong " + privateHost + ":" + privatePort
				+ " -> " + senderHost + ":" + senderPort + " (I'm PR: "
				+ privateHost + ":" + privatePort + ")" + ", seq # " + seqNum);
	}

	protected void resetCount() {
		receiveCount = 0;
	}
}

/**
 * A ping sender for testing purpose. It uses the default public address/port
 * and localhost as it private host. It counts all pings it sends and all pongs
 * it receives.
 */

class CountingPingSender extends PingSenderImpl {

	private static final long serialVersionUID = 1L;

	protected int sendCount = 0;

	protected int receiveCount = 0;

	public CountingPingSender(int privatePort, int timeout) throws IOException {
		super(NetUtil.getLocalHost(), privatePort, InetAddress
				.getByName(TypeCastConstants.DEFAULT_RMI_ADDRESS),
				TypeCastConstants.DEFAULT_RMI_PORT, timeout);
	}

	public int getReceiveCount() {
		return receiveCount;
	}

	public int getSendCount() {
		return sendCount;
	}

	public void onExpectedTimeout(int seqNum) {
		super.onExpectedTimeout(seqNum);
		System.err.println("Expected timeout for seq # " + seqNum);
	}

	public void onInvalidPong(InetAddress senderHost, int senderPort, int seqNum) {
		super.onInvalidPong(senderHost, senderPort, seqNum);
		System.out.println("Received unsolicited pong " + senderHost + ":"
				+ senderPort + " -> " + privateHost + ":" + privatePort
				+ ", seq # " + seqNum);
	}

	public void onUnexpectedTimeout(int seqNum) {
		super.onExpectedTimeout(seqNum);
		System.err.println("Unexpected timeout for seq # " + seqNum);
	}

	public void onValidPong(InetAddress senderHost, int senderPort, int seqNum) {
		super.onValidPong(senderHost, senderPort, seqNum);
		receiveCount++;
		System.err.println("Received pong " + senderHost + ":" + senderPort
				+ " -> " + privateHost + ":" + privatePort + ", seq # "
				+ seqNum);
	}

	public void ping(InetAddress addr, int port, int seqNum) throws IOException {
		super.ping(seqNum);
		sendCount++;
		System.err.println("Sending ping to " + publicHost + ":" + publicPort
				+ ", seq # " + seqNum);
	}

	protected void resetCount() {
		sendCount = 0;
		receiveCount = 0;
	}
}

/**
 * Test case for PingSenderImpl and PingReceiverImpl.
 */

public class PingTestCase extends TestCase {
	protected int rand() {
		return (int) (Math.random() * Integer.MAX_VALUE);
	}

	protected void sleep(int time) {
		try {
			Thread.sleep(time);
		} catch (InterruptedException e) {
			// Ignore
		}
	}

	public void test() throws IOException {
		int receiverCount = 0;
		try {
			for (receiverCount = 1; receiverCount <= 10; receiverCount++) {
				test(30000 + receiverCount * 1000, receiverCount, 5);
			}
		} catch (IOException e) {
			System.err.println("IOException happens when receiverCount = "
					+ receiverCount);
			throw e;
		} catch (AssertionFailedError e) {
			System.err
					.println("AssertionFailedError happens when receiverCount = "
							+ receiverCount);
			throw e;

		}
	}

	/**
	 * Test simple ping/pong situation.
	 * 
	 * @param receiverCount
	 * @param runCount
	 * 
	 * @throws IOException
	 */

	public void test(int portBase, int receiverCount, int runCount)
			throws IOException {

		// Sender sending to DEFAULT_RMI_ADDRESS:DEFAULT_RMI_PORT, listening at
		// localhost:portBase

		CountingPingSender ps;
		try {
			ps = new CountingPingSender(portBase, 10000);
			System.err.println("Binded port " + portBase + " for ping sender");
		} catch (BindException e) {
			System.err.println("Cannot bind port " + portBase
					+ " for ping sender");
			throw e;
		}

		// Receivers listening at DEFAULT_RMI_ADDRESS:DEFAULT_RMI_PORT, sending
		// from localhost:portBase + 1 + i

		CountingPingReceiver[] prs = new CountingPingReceiver[receiverCount];
		for (int i = 0; i < prs.length; i++) {
			try {
				prs[i] = new CountingPingReceiver(portBase + 1 + i);
				System.err.println("Binded port " + (portBase + 1 + i)
						+ " for ping receiver #" + i);
			} catch (BindException e) {
				System.err.println("Cannot bind port " + (portBase + 1 + i)
						+ " for ping receiver #" + i);
				throw e;
			}
		}

		// Sender pings <receiverCount> receiver(s) for <runCount> # of times

		for (int runNum = 1; runNum <= runCount; runNum++) {
			// Reset sender and receivers' counters

			ps.resetCount();
			for (CountingPingReceiver pr : prs) {
				pr.resetCount();
			}

			// Send a ping
			// Wait a short time to let the receivers react

			ps.ping(InetAddress
					.getByName(TypeCastConstants.DEFAULT_RMI_ADDRESS),
					TypeCastConstants.DEFAULT_RMI_PORT, rand());

			sleep(200);

			// Check if we have really sent a ping
			// Note: Send count is accumulative

			assertEquals("sender's ping count @ run #" + runNum + " of "
					+ runCount + " (" + receiverCount + " receivers)", 1, ps
					.getSendCount());

			// Check if each of the receiver has received a ping
			// Note: Receive count is accumulative

			for (int i = 0; i < prs.length; i++) {
				assertEquals("receiver's ping count @ run #" + runNum + " of "
						+ runCount + " (" + receiverCount + " receivers)", 1,
						prs[i].getReceiveCount());
			}

			// Check if the sender has receiverd a pong for each receiver
			// Note: Receive count is accumulative

			assertEquals("sender's pong count @ run #" + runNum + " of "
					+ runCount + " (" + receiverCount + " receivers)",
					receiverCount, ps.getReceiveCount());

			// Wait for a short time before another run

			sleep(200);
		}
		
		// Dispose sender and receivers
		
		ps.dispose();
		for (CountingPingReceiver pr : prs) {
			pr.dispose();
		}
	}
}
